package org.simpleframework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.simpleframework.core.BeanContainer;
import org.simpleframework.core.annotation.Controller;
import org.simpleframework.mvc.RequestProcessorChain;
import org.simpleframework.mvc.annotation.RequestMapping;
import org.simpleframework.mvc.annotation.RequestParm;
import org.simpleframework.mvc.annotation.ResponseBody;
import org.simpleframework.mvc.processor.RequestProcessor;
import org.simpleframework.mvc.render.ResultRender;
import org.simpleframework.mvc.render.impl.JsonResultRender;
import org.simpleframework.mvc.render.impl.ResourceNotFoundResultRender;
import org.simpleframework.mvc.render.impl.ViewResultRender;
import org.simpleframework.mvc.type.ControllerMethod;
import org.simpleframework.mvc.type.RequestMethod;
import org.simpleframework.mvc.type.RequestPathInfo;
import org.simpleframework.util.ConverterUtil;
import org.simpleframework.util.ValidationUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * Controller请求处理器 ,请求经历 pre ->static->jsp都没有被处理,此时就需要controller处理器,将请求信息与Controller方法的映射,进而进行处理.
 * <p>
 * 在该处理器中需要处理的事情: 对象被创建时,应该有Controller中请求与方法的映射关系表,该对象在DisPatchServlet中被创建并放入到list中,当被创建时
 * 需要解析到Controller中所有映射->已经对应的方法的map,留给后续方法映射时进行匹配.
 * </p>
 *
 * @author: Oliver
 * @time: 2020/12/28/23:09
 */
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {
    // 保存 请求方法映射到具体执行的方法map
    private ConcurrentHashMap<RequestPathInfo, ControllerMethod> controllerPathMap = new ConcurrentHashMap<>();
    // ioc容器
    private BeanContainer instance;

    /**
     * 在无惨构造函数中初始化
     * 依靠容器的能力，建立起请求路径、请求方法与Controller方法实例的映射
     */
    public ControllerRequestProcessor() {
        //单例的容器
        this.instance = BeanContainer.getInstance();
        // 获取到所有被Controller标记的类
        Set<Class<?>> controllerClass = instance.getClassesByAnnotation(Controller.class);
        initControllerPathMap(controllerClass);
    }

    /**
     * 根据获取到Class 对象集合  去初始化 controllerPathMap 请求信息 -> Controller方法的映射关系
     *
     * @param classSet 获取到所有被Controller标签标记的类的Class对象
     */
    private void initControllerPathMap(Set<Class<?>> classSet) {
        //校验是否获取到class
        if (ValidationUtil.isEmpty(classSet)) return;
        for (Class<?> controllerClass : classSet) {
            //解析具体的每一个Controller
            resolverController(controllerClass);
        }
    }

    /**
     * 解析Controller类信息 包括@RequestMapping,类中的方法等
     *
     * @param controllerClass
     */
    private void resolverController(Class<?> controllerClass) {
        String prefix = "";
        //获取到方法头上的RequestMapping标签,得到url的前缀
        if (controllerClass.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMappingCls = controllerClass.getAnnotation(RequestMapping.class);
            prefix = requestMappingCls.value();
        }
        // 将方法上的 请求路径前缀统一改造成 /bb/cc 这种格式
        if (prefix != null && prefix.length() > 0 && !prefix.startsWith("/")) {
            prefix = "/" + prefix;
            if (prefix.endsWith("/")) {
                prefix = prefix.substring(0, prefix.length() - 1);
            }
        }

        // 获取类中的所有方法, 进行Controller类中的方法的解析
        Method[] methods = controllerClass.getDeclaredMethods();
        resolverMethod(prefix, methods, controllerClass);

    }

    /**
     * 解析特定Controller下的方法 包括方法参数, 方法返回值等的信息
     *
     * @param prefix          Controller类上的请求路径
     * @param methods         该类里面所有的方法
     * @param controllerClass 具体的controllerClass
     */
    private void resolverMethod(String prefix, Method[] methods, Class<?> controllerClass) {
        if (ValidationUtil.isEmpty(methods)) { //返回 解析下一个类
            return;
        }
        for (Method method : methods) {
            doResolverMethod(prefix, method, controllerClass);
        }
    }

    /**
     * 通过校验以后,解析每一个被RequestMapping标记的方法
     *
     * @param url             请求路径前缀
     * @param method          具体的方法
     * @param controllerClass 具体的controllerClass
     * @Exception RunTimeException 限制每一个方法参数必须要有@RequestParm 注解,如果没有抛出异常.
     */
    private void doResolverMethod(String url, Method method, Class<?> controllerClass) {
        // 获取方法上的注解RequestMapping
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMappingMhd = method.getAnnotation(RequestMapping.class);
            RequestMethod requestMethod = requestMappingMhd.method();
            String subPath = requestMappingMhd.value();
            // 处理路径规范
            if (!subPath.startsWith("/")) {
                subPath = "/" + subPath;
            }
            url = url + subPath;
            if (url.endsWith("/")) {
                url = url.substring(0, url.length() - 1);
            }

            // 解析方法参数以及参数上的注解
            Parameter[] parameters = method.getParameters();
            // 该map主要承接方法上的参数已经参数对应的类型 ,后续根据请求的参数,获取到该类型并给该类型赋值为请求传递过来的数据
            // 当方法没有参数时,则parameterMap 为null 负责一定有参数名->参数类型的对应关系
            Map<String, Class<?>> parameterMap = new HashMap<>(8);
            if (!ValidationUtil.isEmpty(parameters)) {
                for (Parameter parameter : parameters) {
                    RequestParm requestParm = parameter.getAnnotation(RequestParm.class);
                    if (requestParm == null) {
                        throw new RuntimeException("The parameter must have @RequestParam");
                    }
                    String paramName = requestParm.value();
                    Class<?> paramType = parameter.getType();
                    parameterMap.put(paramName, paramType);
                }
            }
            // 组装controllerPathMap
            RequestPathInfo requestPathInfo = new RequestPathInfo(url, String.valueOf(requestMethod));
            ControllerMethod controllerMethod = new ControllerMethod(controllerClass, method, parameterMap);
            // 可能存在重复数据覆盖情况,一般不会走到, 如果走到这里,打印出警告.
            if (this.controllerPathMap.containsKey(requestPathInfo)) {
                log.warn("duplicate url:{} registration，current class {} method{} will override the former one",
                        requestPathInfo.getPath(), controllerClass.getName(), method.getName());
            }
            controllerPathMap.put(requestPathInfo, controllerMethod);
        }
    }

    /**
     * 根据 RequestInfo 在映射表中查找Controller对应的处理方法,进行处理
     *
     * @param chain 调用链,用于结果处理参数的组装,以及处理时必要的参数信息传递
     * @return
     * @throws Exception
     */
    @Override
    public boolean process(RequestProcessorChain chain) throws Exception {
        RequestPathInfo pathInfoKey = new RequestPathInfo(chain.getMethodPath(), chain.getMethod());
        ControllerMethod controllerMethod = controllerPathMap.get(pathInfoKey);
        if (controllerMethod == null) {
            // 查找不到对应路径,访问路径无效 404 交由NotFount结果处理器进行处理
            chain.setRender(new ResourceNotFoundResultRender(chain.getMethodPath(), chain.getMethod()));
            return false;
        }
        // 执行对应的ControllerMethod
        Object result = invokerMethod(controllerMethod, chain.getRequest());

        //设置 ResultRender
        setResultRend(controllerMethod, chain, result);
        return false;
    }

    /**
     * 处理完成之后 给chain中的 resultRender进行设值,使得chain可以调用指定resultRender,进行结果的处理响应
     *
     * @param controllerMethod controllerMethod
     * @param chain            RequestProcessorChain
     * @param result           返回值
     */
    private void setResultRend(ControllerMethod controllerMethod, RequestProcessorChain chain, Object result) {

        if (result == null) {
            return; // 默认会给 render 设置 DefaultRender
        }
        ResponseBody responseBody = controllerMethod.getMethod().getAnnotation(ResponseBody.class);
        ResultRender resultRender;
        if (responseBody == null) {
            resultRender = new ViewResultRender(result);
        } else {
            resultRender = new JsonResultRender(result);
        }
        //最终会将结果处理器交给RequestProcessorChain 去处理
        chain.setRender(resultRender);
    }

    /**
     * 执行controllerMethod中的方法
     *
     * @param controllerMethod controllerMethod
     * @param request
     */
    private Object invokerMethod(ControllerMethod controllerMethod, HttpServletRequest request) {
        // 解析请求参数 目前参数只支持1-1 不支持 1-string[]
        Map<String, String[]> parameterMap = request.getParameterMap();
        Map<String, String> requestParamsMap = new HashMap<>(16);
        if (!ValidationUtil.isEmpty(parameterMap)) {
            for (Map.Entry<String, String[]> keySet : parameterMap.entrySet()) {
                if (!ValidationUtil.isEmpty(keySet.getValue())) {
                    requestParamsMap.put(keySet.getKey(), keySet.getValue()[0]);
                }
            }
        }

        // 根据解析的请求参数与controllerMethod中参数名-参数类型 去实例化出方法对应的参数
        Map<String, Class<?>> methodParameters = controllerMethod.getMethodParameters();
        List<Object> methodParams = new ArrayList<>();
        for (Map.Entry<String, Class<?>> entry : methodParameters.entrySet()) {
            String param = entry.getKey();
            Class<?> paramType = entry.getValue();
            String requestValue = requestParamsMap.get(param);
            Object value;
            //只支持String 以及基础类型char,int,short,byte,double,long,float,boolean,及它们的包装类型
            if (requestValue == null) { // 没有传递过来 则转成对应的null值
                value = ConverterUtil.primitiveNull(paramType);
            } else {
                value = ConverterUtil.convert(paramType, requestValue);
            }
            methodParams.add(value);
        }

        Class<?> controllerClass = controllerMethod.getControllerClass();
        Object controllerInstance = instance.getBean(controllerClass);
        Method method = controllerMethod.getMethod();
        Object res;
        try {
            if (methodParams.size() == 0) {
                res = method.invoke(controllerInstance);
            } else {
                res = method.invoke(controllerInstance, methodParams.toArray());
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            //如果是调用异常的话，需要通过e.getTargetException() 去获取执行方法抛出的异常
            throw new RuntimeException(e.getTargetException());
        }
        return res;
    }
}
